#include "SIPClient2.h"

#include <algorithm>
#include <cctype>
#include <sstream>
#include <iostream>
#include "../rtmp/cmonitor.h"

using std::cout;
using std::ostringstream;


inline string & __strtolower(string &str)
{
	std::transform(str.begin(), str.end(), str.begin(), tolower);
	return str;
}


CSipClient2::CSipClient2()
{
    m_bConnected = false;
    m_bInitialized = false;
    m_iStatus = SipStatus::SS_INITIAL;
}

CSipClient2::~CSipClient2()
{
    while(!m_qPacketQueue.empty())
    {
        delete (m_qPacketQueue.front());
        m_qPacketQueue.pop();
    }
    //
    if(m_sipLastSentRequest != NULL)
        delete m_sipLastSentRequest;
    //
    if(m_sipLastRecievedResponse != NULL)
        delete m_sipLastRecievedResponse;
}

bool
CSipClient2::Connect(const string &strSipServerAddress, int iSipServerPort, int iSipClientPort)
{
    m_bSipServerAddressChanged = false;
    //
    m_strSipServerAddress = strSipServerAddress;
    m_iSipServerPort = iSipServerPort;
    m_iSipClientPort = iSipClientPort;
    //
    try
    {
        m_server.StartServer(m_iSipClientPort);
        m_client.ConnectToHost(m_strSipServerAddress, m_iSipServerPort);
    }
    catch(SocketException *ex)
    {
        std::cerr << std::endl
                  << "+---------------------------------------+\n"
                  << "| [SIP] CHYBA pri inicializaci spojeni! |\n"
                  << "+---------------------------------------+\n"
                  << std::endl;
        //
        delete ex;
        return false;
    }
    //
    m_bConnected = true;
    return true;
}

void
CSipClient2::Close()
{
    m_server.StopServer();
    m_client.DisconnectFromHost();
    m_bConnected = false;
}

bool
CSipClient2::Init(fd_set *fdsRead, fd_set *fdsWrite)
{
    if(((m_fdsRead = fdsRead) == NULL) || ((m_fdsWrite = fdsWrite) == NULL))
        return false;
    //
    return (m_bInitialized = true);
}

bool
CSipClient2::SetFd()
{
    if(!m_bInitialized)
        return false;


    //
    FD_SET(m_server.GetSocket(), m_fdsRead);
    FD_SET(m_client.GetSocket(), m_fdsRead);

    //
    if(m_qPacketQueue.size() > 0)	// do I need something to write?
        FD_SET(m_client.GetSocket(), m_fdsWrite);

    //
    return true;
}

bool CSipClient2::IsConnected()
{
    return m_bConnected;
}

void
CSipClient2::Register(const string &strUserId, const string &strSipClientAddress,
                      const string &strScreenName, const string &strAuthUser, const string &strAuthPasswd)
{
    m_strUserId = strUserId;
    m_strScreenName = strScreenName;
    m_strAuthUser = strAuthUser;
    m_strAuthPasswd = strAuthPasswd;
    m_strSipClientAddress = strSipClientAddress;
    //
    m_sipLastSentRequest = new CSipRequest(m_strUserId, m_strSipClientAddress, m_iSipClientPort, m_strSipServerAddress);
    m_sipLastRecievedResponse = new CSipResponse(m_strUserId, m_strSipClientAddress, m_iSipClientPort, m_strSipServerAddress);
    //
    m_sipLastSentRequest->SetCallerScreenName(m_strScreenName);
    m_sipLastRecievedResponse->SetCallerScreenName(m_strScreenName);
    //
    m_sipLastSentRequest->GetAuthorization().SetUser(m_strAuthUser);
    m_sipLastSentRequest->GetAuthorization().SetPasswd(m_strAuthPasswd);
    //
    m_sipLastSentRequest->GetProxyAuthorization().SetUser(m_strAuthUser);
    m_sipLastSentRequest->GetProxyAuthorization().SetPasswd(m_strAuthPasswd);
    //
    PushRegister(1);
    m_iStatus = SipStatus::SS_REGISTERING;
}

void
CSipClient2::Invite(const string &strCalleeId, const string &strCalleeScreenName,
        const string &strCalleeServerAddress, const string &strNearRtpAddress, int iNearRtpPort, int iRtpSessionId)
{
    m_strCalleeId = strCalleeId;
    m_strCalleeScreenName = strCalleeScreenName;
    m_strCalleeServerAddress = strCalleeServerAddress;
    m_strNearRtpAddress = strNearRtpAddress;
    m_iNearRtpPort = iNearRtpPort;
    m_iRtpSessionId = iRtpSessionId;
    //
    m_sipLastSentRequest->SetCalleeId(m_strCalleeId);
    m_sipLastRecievedResponse->SetCalleeId(m_strCalleeId);
    //
    m_sipLastSentRequest->SetCalleeScreenName(m_strCalleeScreenName);
    m_sipLastRecievedResponse->SetCalleeScreenName(m_strCalleeScreenName);
    //
    PushInvite(1);
    m_iStatus = SipStatus::SS_INVITING;
}

void
CSipClient2::Cancel()
{
    PushCancel(1);
    m_iStatus = SipStatus::SS_HANGING_UP;
}

void
CSipClient2::Bye()
{
    if(m_sipLastSentRequest->GetCommand() == "BYE")
        PushBye(m_sipLastSentRequest->GetCSeq());
    else
        PushBye(m_sipLastSentRequest->GetCSeq() + 1);
    //
    m_iStatus = SipStatus::SS_HANGING_UP;
}

bool
CSipClient2::RecieveResponse()
{
    std::cout << "RecieveResponse()\n";
    //
    SOCKET socket = 0;
    sockaddr ci;
    try
    {
        if(FD_ISSET(m_server.GetSocket(), m_fdsRead))
        {
            socket = m_server.GetSocket();
            std::cout << "socket = m_server.GetSocket();\n";
            //
            m_sipLastRecievedResponse->ClearBuffer();
            m_server.RecieveData(socket, &ci, m_sipLastRecievedResponse);
            m_sipLastRecievedResponse->Update(m_sipLastRecievedResponse->GetBuffer());
        }
        else if(FD_ISSET(m_client.GetSocket(), m_fdsRead))
        {
            socket = m_client.GetSocket();
            std::cout << "socket = m_client.GetSocket();\n";
            //
            m_sipLastRecievedResponse->ClearBuffer();
            m_client.RecieveData(m_sipLastRecievedResponse);
            m_sipLastRecievedResponse->Update(m_sipLastRecievedResponse->GetBuffer());
        }
    }
    catch(SocketException *ex)
    {
        std::cerr << std::endl
                  << "+------------------------------------+\n"
                  << "| [SIP] CHYBA pri prijeti pozadavku! |\n"
                  << "+------------------------------------+\n"
                  << std::endl;
        //
        delete ex;
        return false;
    }
    //
    if(socket == 0)
        return false;
    //
    //
    // check if it's response to the last sent request...others don't interest me at all
    if(m_sipLastSentRequest->GetCSeq() > m_sipLastRecievedResponse->GetCSeq())	// delayed response
            return false;															// - no need to be processed
    //
    // check call-id, tags and branch; if there is some difference, then some foreign transaction arrived
    if(m_sipLastSentRequest->GetCallId() != m_sipLastRecievedResponse->GetCallId())
            return false;
    //
    if(m_sipLastSentRequest->GetBranch() != m_sipLastRecievedResponse->GetBranch())
            return false;
    //
    if(m_sipLastSentRequest->GetCallerTag() != m_sipLastRecievedResponse->GetCallerTag())
            return false;
    //
/* check from anf to tags
   ----------------------
    if(m_sipLastSentRequest->GetCalleeTag().length() == 0)	// callee's tag wasn't set yet?
        m_sipLastSentRequest->SetCalleeTag(m_sipLastRecievedResponse->GetCalleeTag());
    else if(m_sipLastSentRequest->GetCalleeTag() != m_sipLastRecievedResponse->GetCalleeTag())
        return false;
*/
    //
    std::cout << "socket: " << socket << std::endl;
    std::cout << "recieved command/status: " << m_sipLastRecievedResponse->GetCommand()
              << m_sipLastRecievedResponse->GetStatus() << std::endl;
    //
    return true;
}

int
CSipClient2::ProcessResponse()
{
    SendRequestQueue();
    if(!RecieveResponse())
    {
        if((m_iStatus == SipStatus::SS_HANGING_UP) && (m_sipLastSentRequest->GetCommand() == "BYE"))    // posilam bye, dokud druha strana nezavesi
        {
            PushBye(m_sipLastSentRequest->GetCSeq());
        }
        //
        return -1;
    }
    std::cout << "recv ok" << std::endl;
    //
    // call termination requested?
    if(m_sipLastRecievedResponse->GetCommand() == "BYE")
    {
        PushOk(m_sipLastRecievedResponse->GetCSeq());
        return (m_iStatus = SipStatus::SS_HANGED_UP);
    }

    if(m_sipLastRecievedResponse->GetCommand() == "CANCEL")
    {
        PushOk(m_sipLastRecievedResponse->GetCSeq());
        return (m_iStatus = SipStatus::SS_HANGED_UP);
    }
    //
    switch(m_sipLastRecievedResponse->GetStatusCode())
    {
        case SipResponse::RINGING:
            if(m_iStatus == SipStatus::SS_HANGING_UP)   // zavesil jsem pri invitu, ale ke druhy srane to nedoslo --> opakovat
                PushCancel(1);
            else
                m_iStatus = SipStatus::SS_RINGING;
            break;

        case SipResponse::MULTIPLE_CHOICES:
            // TODO:
            //   send to rtmp client
            //   pick one
            //   alter callee
            //   resend request
            m_iStatus = SipStatus::SS_ERROR;
            throw(new CSipException(SipError::COMMUNICATION_TERMINATION));
            break;

        case SipResponse::MOVED_PERMANENTLY:
            m_bSipServerAddressChanged = true;
            m_strSipServerAddress = m_sipLastRecievedResponse->GetContactAddress();
        //
        case SipResponse::MOVED_TEMPORARILY:
            m_sipLastSentRequest->SetSipServerAddress(m_sipLastRecievedResponse->GetContactAddress());
            PushSipPacket(new CSipRequest(*m_sipLastSentRequest));
            break;

        case SipResponse::USE_PROXY:
            // TODO:
            //   add proxy to request
            //   resend request
            m_iStatus = SipStatus::SS_ERROR;
            throw(new CSipException(SipError::COMMUNICATION_TERMINATION));
            break;

        case SipResponse::UNAUTHORIZED:
            if(!m_sipLastSentRequest->IsAuthorizationRequired())	// need to be authorized
            {
                PushRegister(m_sipLastRecievedResponse->GetCSeq() + 1, true);
            }
            else	// authorization wasn't successful - bad login or bad format of request,
            {		// because there is implemented only Digest auth MD5 request type
                m_iStatus = SipStatus::SS_ERROR;
                throw(new CSipException(SipError::COMMUNICATION_TERMINATION));
            }
            break;

        case SipResponse::PROXY_AUTHENTICATION_REQUIRED:
            if(1) //(!m_sipLastSentRequest->IsProxyAuthorizationRequired())	// need to be authorized
            {
                PushAck(m_sipLastRecievedResponse->GetCSeq());
                PushInvite(m_sipLastRecievedResponse->GetCSeq() + 1, true);
            }
            else	// authorization wasn't successful - bad login or bad format of request,
            {		// because there is implemented only Digest auth MD5 request type
                m_iStatus = SipStatus::SS_ERROR;
                throw(new CSipException(SipError::COMMUNICATION_TERMINATION));
            }
            break;

        case SipResponse::REQUEST_TIMEOUT:
            PushSipPacket(new CSipRequest(*m_sipLastSentRequest));
            break;

        case SipResponse::REQUEST_ENTITY_TOO_LARGE:
            // if rety-after field
            //   wait && resend request
            // else
            //   error
            m_iStatus = SipStatus::SS_ERROR;
            throw(new CSipException(SipError::COMMUNICATION_TERMINATION));
            break;

        case SipResponse::TEPORARILY_UNAVAILABLE:	// far sip client hanged up
        case SipResponse::DECLINE:
            m_iStatus = SipStatus::SS_HANGED_UP;
            break;

        case SipResponse::REQUEST_TERMINATED:		// far rtmp client hanged up and then this sip client sent CANCEL request
            m_iStatus = SipStatus::SS_HANGED_UP;
            break;

        default:
            if( (m_sipLastRecievedResponse->GetStatusCode() >= 100) &&
                (m_sipLastRecievedResponse->GetStatusCode() <= 199))	// 1xx
            {
                // keep waiting for some other response
            }
            else if((m_sipLastRecievedResponse->GetStatusCode() >= 200) &&
                    (m_sipLastRecievedResponse->GetStatusCode() <= 299))	// 2xx
            {
                switch(m_iStatus)
                {
                    case SipStatus::SS_REGISTERING:
                        m_iStatus = SipStatus::SS_REGISTERED;
                        break;

                    case SipStatus::SS_INVITING:
                    case SipStatus::SS_RINGING:
                        m_strFarRtpAddress = m_sipLastRecievedResponse->GetRtpAddress();
                        m_iFarRtpPort = m_sipLastRecievedResponse->GetRtpPort();
                        m_iStatus = SipStatus::SS_CALL_ACCEPTED;
                        PushAck(m_sipLastRecievedResponse->GetCSeq());
                        break;

                    case SipStatus::SS_HANGING_UP:  // ja jsem posilal Bye
                        m_iStatus = SipStatus::SS_HANGED_UP;
                        break;

                    // Following responses are multiple responses, which mean, that some of following requests
                    // were lost somewhere between client and server (caused by UDP transport) => resend
                    case SipStatus::SS_CALL_ACCEPTED:
                    case SipStatus::SS_CALLING:
                        PushAck(m_sipLastRecievedResponse->GetCSeq());
                        break;

                    case SipStatus::SS_HANGED_UP:
                        PushOk(m_sipLastRecievedResponse->GetCSeq());
                        break;

                    default:
                        // nothing to do
                        //   - a redundant or delayed packet (caused by UDP transport)
                        //     or the client is already in final state
                        break;
                }
            }
            else    // 4xx, 5xx, 6xx
            {
                m_iStatus = SipStatus::SS_HANGED_UP;
                std::cout << "\nERROR response from server --> SipStatus::SS_HANGED_UP" << std::endl;
                //m_iStatus = SipStatus::SS_ERROR;
                //throw(new CSipException(SipError::COMMUNICATION_TERMINATION));
            }
            break;
    }
    return m_iStatus;
}

void
CSipClient2::SendRequestQueue()
{
    if(!FD_ISSET(m_client.GetSocket(), m_fdsWrite)) // nemam moznost zapisovat
        return;
    //
    try
    {
        CSipPacket *tmp;
        while(!m_qPacketQueue.empty())
        {
            tmp = m_qPacketQueue.front();
            m_client.SendData(tmp);
            // no exception was thrown...ok
            m_qPacketQueue.pop();
            if(m_sipLastSentRequest)
                delete m_sipLastSentRequest;
            //
            m_sipLastSentRequest = (CSipRequest *)tmp;	// TODO: muze delat neplechu
        }
    }
    catch(SocketException *ex)
    {
        std::cerr << std::endl
                  << "+---------------------------------------------+\n"
                  << "| [SIP] CHYBA pri odesilani fronty pozadavku! |\n"
                  << "+---------------------------------------------+\n"
                  << std::endl;
        //
        delete ex;
        return;
    }
}

void
CSipClient2::PushLastRequest()
{
    PushSipPacket(m_sipLastSentRequest);
}

void
CSipClient2::PushSipPacket(CSipPacket *p)
{
    m_qPacketQueue.push(p);
}

void
CSipClient2::PushRegister(int iCSeq, bool bAuthRequired)
{
	CSipRequest *req = new CSipRequest(*m_sipLastSentRequest);
	req->SetCommand("REGISTER");
	req->SetCalleeTag("");	// don't use callee tag, because this is 1st request of the dialog
	req->SetContent("");
	req->SetCSeq(iCSeq);
	if(bAuthRequired)
	{
		req->SetAuthorizationRequired(true);
		req->GetAuthorization().SetDomain(m_sipLastRecievedResponse->GetAuthorization().GetDomain());
		req->GetAuthorization().SetNonce(m_sipLastRecievedResponse->GetAuthorization().GetNonce());
		req->GetAuthorization().SetOpaque(m_sipLastRecievedResponse->GetAuthorization().GetOpaque());
		req->GetAuthorization().SetQop(m_sipLastRecievedResponse->GetAuthorization().GetQop());
		req->GetAuthorization().SetRealm(m_sipLastRecievedResponse->GetAuthorization().GetRealm());
		req->GetAuthorization().SetStale(m_sipLastRecievedResponse->GetAuthorization().GetStale());
		req->GetAuthorization().SetType(m_sipLastRecievedResponse->GetAuthorization().GetType());
	}
	else
		req->SetAuthorizationRequired(false);
	//
	req->BuildPacket();
	//
	PushSipPacket(req);
}

void
CSipClient2::PushInvite(int iCSeq, bool bProxyAuthRequired)
{
	CSipRequest *req = new CSipRequest(*m_sipLastSentRequest);
	req->SetCommand("INVITE");
	req->SetCalleeTag("");	// don't use callee tag, because this is 1st request of the dialog
        ostringstream sdp;
        sdp.clear();
        sdp.str("");
        sdp << "v=0\r\n"
            << "o=" << m_strUserId << " " << m_iRtpSessionId << " 2 IN IP4 " << m_strNearRtpAddress << "\r\n"
            << "s=NEHET VoIP\r\n"
            << "c=IN IP4 " << m_strNearRtpAddress << "\r\n"
            << "t=0 0\r\n"
            << "m=audio " << m_iNearRtpPort << " RTP/AVP 96\r\n"
            << "a=rtpmap:96 Speex/8000/1\r\n"
            << "a=fmtp:96 mode=\"any\"\r\n"
            << "a=sendrecv\r\n"
        ;
        req->SetContent(sdp.str());
	req->SetCSeq(iCSeq);
	if(bProxyAuthRequired)
	{
		req->SetProxyAuthorizationRequired(true);
		req->GetProxyAuthorization().SetDomain(m_sipLastRecievedResponse->GetProxyAuthorization().GetDomain());
		req->GetProxyAuthorization().SetNonce(m_sipLastRecievedResponse->GetProxyAuthorization().GetNonce());
		req->GetProxyAuthorization().SetOpaque(m_sipLastRecievedResponse->GetProxyAuthorization().GetOpaque());
		req->GetProxyAuthorization().SetQop(m_sipLastRecievedResponse->GetProxyAuthorization().GetQop());
		req->GetProxyAuthorization().SetRealm(m_sipLastRecievedResponse->GetProxyAuthorization().GetRealm());
		req->GetProxyAuthorization().SetStale(m_sipLastRecievedResponse->GetProxyAuthorization().GetStale());
		req->GetProxyAuthorization().SetType(m_sipLastRecievedResponse->GetProxyAuthorization().GetType());
	}
	else
		req->SetProxyAuthorizationRequired(false);
	//
	req->BuildPacket();
	//
	PushSipPacket(req);
}

void
CSipClient2::PushCancel(int iCSeq)
{
	CSipRequest *req = new CSipRequest(*m_sipLastSentRequest);
	req->SetCommand("CANCEL");
	req->SetContent("");
	req->SetCSeq(iCSeq);
	req->BuildPacket();
	//
	PushSipPacket(req);
}

void
CSipClient2::PushAck(int iCSeq)
{
	CSipRequest *req = new CSipRequest(*m_sipLastSentRequest);
	req->SetCommand("ACK");
  req->SetCalleeTag(m_sipLastRecievedResponse->GetCalleeTag());
	req->SetContent("");
	req->SetCSeq(iCSeq);
	req->BuildPacket();
	//
	PushSipPacket(req);
}

void
CSipClient2::PushBye(int iCSeq)
{
	CSipRequest *req = new CSipRequest(*m_sipLastSentRequest);
	req->SetCommand("BYE");
  //req->SetCalleeTag(m_sipLastSentRequest->GetCallerTag());	// don't use callee tag, because this is 1st request of the dialog
	req->SetContent("");
  req->SetCSeq(iCSeq);
	req->BuildPacket();
	//
	PushSipPacket(req);
}

void
CSipClient2::PushOk(int iCSeq)
{
	CSipResponse *req = new CSipResponse(*m_sipLastRecievedResponse);
	req->SetStatusCode(200);
	req->SetStatus("OK");
	req->SetContent("");
	req->SetCSeq(iCSeq);
	req->SetCSeqCommand(m_sipLastSentRequest->GetCommand());
	req->BuildPacket();
	//
	PushSipPacket(req);
}
